﻿using Grand.Business.Core.Interfaces.Common.Localization;
using Grand.Web.Common.Extensions;
using Grand.Web.Common.Page.Paging;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Routing;
using CommonExtensions = Grand.Infrastructure.Extensions.CommonExtensions;

namespace Grand.Web.Common.TagHelpers;

[HtmlTargetElement("page-navigation", TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("page-navigation", Attributes = QueryParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = PaginationParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = ShowTotalSummaryParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = ShowPagerItemsParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = ShowFirstParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = ShowPreviousParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = ShowNextParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = ShowLastParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = ShowIndividualPagesParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = RenderEmptyParametersParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = IndividualPagesDisplayedCountParamAttributeName)]
[HtmlTargetElement("page-navigation", Attributes = BooleanParamAttributeName)]
public class PageNavigationTagHelper : TagHelper
{
    private const string QueryParamAttributeName = "asp-query-param";

    private const string PaginationParamAttributeName = "asp-pagination";

    private const string ShowTotalSummaryParamAttributeName = "asp-show-total-summary";

    private const string ShowPagerItemsParamAttributeName = "asp-show-pager-items";

    private const string ShowFirstParamAttributeName = "asp-show-first";

    private const string ShowPreviousParamAttributeName = "asp-show-previous";

    private const string ShowNextParamAttributeName = "asp-show-next";

    private const string ShowLastParamAttributeName = "asp-show-last";

    private const string ShowIndividualPagesParamAttributeName = "asp-show-individual-pages";


    private const string RenderEmptyParametersParamAttributeName = "asp-render-empty-parameters";

    private const string IndividualPagesDisplayedCountParamAttributeName = "asp-individual-pages-displayed-count";

    private const string BooleanParamAttributeName = "asp-boolean-params";
    private readonly IHtmlHelper _htmlHelper;

    private readonly ITranslationService _translationService;

    public PageNavigationTagHelper(ITranslationService translationService, IHtmlHelper htmlHelper)
    {
        _translationService = translationService;
        _htmlHelper = htmlHelper;
    }

    [ViewContext] public ViewContext ViewContext { get; set; }

    [HtmlAttributeName(QueryParamAttributeName)]
    public string QueryParam { get; set; } = "page";

    [HtmlAttributeName(PaginationParamAttributeName)]
    public IPageableModel Pagination { get; set; }

    [HtmlAttributeName(ShowTotalSummaryParamAttributeName)]
    public bool ShowTotalSummary { get; set; }

    [HtmlAttributeName(ShowPagerItemsParamAttributeName)]
    public bool ShowPagerItems { get; set; } = true;

    [HtmlAttributeName(ShowFirstParamAttributeName)]
    public bool ShowFirst { get; set; } = true;

    [HtmlAttributeName(ShowPreviousParamAttributeName)]
    public bool ShowPrevious { get; set; } = true;

    [HtmlAttributeName(ShowNextParamAttributeName)]
    public bool ShowNext { get; set; } = true;

    [HtmlAttributeName(ShowLastParamAttributeName)]
    public bool ShowLast { get; set; } = true;

    [HtmlAttributeName(ShowIndividualPagesParamAttributeName)]
    public bool ShowIndividualPages { get; set; } = true;

    [HtmlAttributeName(RenderEmptyParametersParamAttributeName)]
    public bool RenderEmptyParameters { get; set; } = true;

    [HtmlAttributeName(IndividualPagesDisplayedCountParamAttributeName)]
    public int IndividualPagesDisplayedCount { get; set; } = 5;

    [HtmlAttributeName(BooleanParamAttributeName)]
    public IList<string> BooleanParameterNames { get; set; } = new List<string>();

    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.SuppressOutput();

        (_htmlHelper as IViewContextAware)?.Contextualize(ViewContext);

        var links = GenerateHtmlString();

        if (string.IsNullOrEmpty(links)) return Task.CompletedTask;
        var sb = new StringBuilder();
        sb.AppendFormat("<nav aria-label=\"Page navigation\">");
        sb.Append(Environment.NewLine);
        sb.AppendFormat(links);
        sb.Append(Environment.NewLine);
        sb.AppendFormat("</nav>");
        sb.Append(Environment.NewLine);
        output.Content.SetHtmlContent(sb.ToString());
        return Task.CompletedTask;
    }

    public virtual string GenerateHtmlString()
    {
        if (Pagination.TotalItems == 0)
            return null;

        var links = new StringBuilder();
        if (ShowTotalSummary && Pagination.TotalPages > 0)
        {
            links.Append("<li class=\"total-summary\">");
            links.Append(string.Format(_translationService.GetResource("Pager.CurrentPage"),
                Pagination.PageIndex + 1, Pagination.TotalPages, Pagination.TotalItems));
            links.Append("</li>");
        }

        if (ShowPagerItems && Pagination.TotalPages > 1)
        {
            if (ShowFirst)
                //first page
                if (Pagination.PageIndex >= 3 && Pagination.TotalPages > IndividualPagesDisplayedCount)
                    links.Append(CreatePageLink(1, _translationService.GetResource("Pager.First"),
                        "first-page page-item"));

            if (ShowPrevious)
                //previous page
                if (Pagination.PageIndex > 0)
                    links.Append(CreatePageLink(Pagination.PageIndex,
                        _translationService.GetResource("Pager.Previous"), "previous-page page-item"));

            if (ShowIndividualPages)
            {
                //individual pages
                var firstIndividualPageIndex = GetFirstIndividualPageIndex();
                var lastIndividualPageIndex = GetLastIndividualPageIndex();
                for (var i = firstIndividualPageIndex; i <= lastIndividualPageIndex; i++)
                    if (Pagination.PageIndex == i)
                        links.Append(
                            $"<li class=\"current-page page-item\"><a class=\"page-link\">{i + 1}</a></li>");
                    else
                        links.Append(CreatePageLink(i + 1, (i + 1).ToString(), "individual-page page-item"));
            }

            if (ShowNext)
                //next page
                if (Pagination.PageIndex + 1 < Pagination.TotalPages)
                    links.Append(CreatePageLink(Pagination.PageIndex + 2,
                        _translationService.GetResource("Pager.Next"), "next-page page-item"));

            if (ShowLast)
                //last page
                if (Pagination.PageIndex + 3 < Pagination.TotalPages &&
                    Pagination.TotalPages > IndividualPagesDisplayedCount)
                    links.Append(CreatePageLink(Pagination.TotalPages,
                        _translationService.GetResource("Pager.Last"), "last-page page-item"));
        }

        var result = links.ToString();
        if (!string.IsNullOrEmpty(result)) result = "<ul class=\"pagination\">" + result + "</ul>";

        return result;
    }

    protected virtual string CreatePageLink(int pageNumber, string text, string cssClass)
    {
        var liBuilder = new TagBuilder("li");
        if (!string.IsNullOrWhiteSpace(cssClass))
            liBuilder.AddCssClass(cssClass);

        var aBuilder = new TagBuilder("a");
        aBuilder.InnerHtml.AppendHtml(text);
        aBuilder.AddCssClass("page-link");
        aBuilder.MergeAttribute("href", CreateDefaultUrl(pageNumber));

        liBuilder.InnerHtml.AppendHtml(aBuilder);
        return liBuilder.RenderHtmlContent();
    }

    protected virtual string CreateDefaultUrl(int pageNumber)
    {
        var routeValues = new RouteValueDictionary();

        var parametersWithEmptyValues = new List<string>();
        foreach (var key in _htmlHelper.ViewContext.HttpContext.Request.Query.Keys)
        {
            var value = _htmlHelper.ViewContext.HttpContext.Request.Query[key].ToString();
            if (RenderEmptyParameters && string.IsNullOrEmpty(value))
            {
                parametersWithEmptyValues.Add(key);
            }
            else
            {
                if (BooleanParameterNames.Contains(key, StringComparer.OrdinalIgnoreCase))
                    //find more info here: http://www.mindstorminteractive.com/pages/jquery-fix-asp-net-mvc-checkbox-truefalse-value/
                    if (!string.IsNullOrEmpty(value) &&
                        value.Equals("true,false", StringComparison.OrdinalIgnoreCase))
                        value = "true";

                routeValues[key] = value;
            }
        }

        if (pageNumber > 1)
        {
            routeValues[QueryParam] = pageNumber;
        }
        else
        {
            //SEO. we do not render pageindex query string parameter for the first page
            if (routeValues.ContainsKey(QueryParam)) routeValues.Remove(QueryParam);
        }

        var url =
            $"{ViewContext.HttpContext.Request.Scheme}://{ViewContext.HttpContext.Request.Host}{ViewContext.HttpContext.Request.Path}";
        url = routeValues.Aggregate(url,
            (current, routeValue) =>
                CommonExtensions.ModifyQueryString(current, routeValue.Key, routeValue.Value?.ToString()));

        if (!RenderEmptyParameters || !parametersWithEmptyValues.Any()) return url;

        return parametersWithEmptyValues.Aggregate(url,
            (current, key) => CommonExtensions.ModifyQueryString(current, key, null));
    }

    protected virtual int GetFirstIndividualPageIndex()
    {
        if (Pagination.TotalPages < IndividualPagesDisplayedCount ||
            Pagination.PageIndex - IndividualPagesDisplayedCount / 2 < 0)
            return 0;

        if (Pagination.PageIndex + IndividualPagesDisplayedCount / 2 >= Pagination.TotalPages)
            return Pagination.TotalPages - IndividualPagesDisplayedCount;

        return Pagination.PageIndex - IndividualPagesDisplayedCount / 2;
    }

    protected virtual int GetLastIndividualPageIndex()
    {
        var num = IndividualPagesDisplayedCount / 2;
        if (IndividualPagesDisplayedCount % 2 == 0) num--;

        if (Pagination.TotalPages < IndividualPagesDisplayedCount ||
            Pagination.PageIndex + num >= Pagination.TotalPages)
            return Pagination.TotalPages - 1;

        if (Pagination.PageIndex - IndividualPagesDisplayedCount / 2 < 0) return IndividualPagesDisplayedCount - 1;

        return Pagination.PageIndex + num;
    }
}